package com.demo.nio.Selector;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Objects;

public class MySelector {

    public static void main(String[] args) throws IOException {
//        blockMode();
//        nonBlockMode();
        receiverServer();
    }

    /**
     * 阻塞模式
     * 缺点：由于 accept & read 是阻塞方法，因此当client数量多时，各个client之间会互现影响
     *
     * @throws IOException
     */
    private static void blockMode() throws IOException {
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //创建服务器
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //绑定端口
        ssc.bind(new InetSocketAddress(8080));

        ArrayList<SocketChannel> channels = new ArrayList<>();
        while (true) {
            //建立与客户端的连接  by 三次握手
            //默认为一个阻塞的方法，使得线程暂停，直到建立了一个连接
            SocketChannel sc = ssc.accept();
            channels.add(sc);

            for (SocketChannel channel : channels) {
                //默认为一个阻塞方法，使得线程暂停，直到读到了数据
                channel.read(buffer);
                buffer.flip();
                //由于要多次使用，故而每次使用后clear
                buffer.clear();
            }
        }
    }

    /**
     * 非阻塞模式
     * ssc.configureBlocking(false) 将ssc设置为非阻塞模式
     * -> ssc.accept() 即便没有获取到连接，该线程也会继续执行下去
     * sc.configureBlocking(false) 将sc设置为非阻塞模式
     * -> channel.read(buffer) 即便没有获得信息，该线程也会继续运行下去
     * <p>
     * 优点：各个client 之间不会相互影响
     * 缺点：非常损耗cpu
     *
     * @throws IOException
     */
    private static void nonBlockMode() throws IOException {
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //设置为非阻塞模式
        ssc.configureBlocking(false);
        ssc.bind(new InetSocketAddress(8080));

        ArrayList<SocketChannel> channels = new ArrayList<>();
        while (true) {

            SocketChannel sc = ssc.accept();
            //此处sc 可能没有获取到，故而要做判断
            if (Objects.nonNull(sc)) {
                //设置为非阻塞模式
                sc.configureBlocking(false);
                channels.add(sc);
            }
            channels.forEach(channel -> {
                int read;
                try {
                    read = channel.read(buffer);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                //上面read可能没有数据，故而要做判断
                if (read > 0) {
                    buffer.flip();
                    buffer.clear();
                }
            });
        }
    }

    //selector 模式
    private static void receiverServer() throws IOException {
        Selector selector = Selector.open();
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(8080));
        //非阻塞模式
        ssc.configureBlocking(false);

        SelectionKey sscKey = ssc.register(selector, 0, null);
        //设置关注事件
        sscKey.interestOps(SelectionKey.OP_ACCEPT);

        while (true) {
            //没有事件会阻塞，有事件会运行
            //注意事件必须被 处理 | 取消，不然会一直被select
            selector.select();
            //处理事件，即可以处理所有accept 的clients
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {

                SelectionKey key = iterator.next();

                if (key.isAcceptable()) {
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    SocketChannel sc = channel.accept();
                    //设置为非阻塞模式
                    sc.configureBlocking(false);

                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    //将sc注册到selector 上当事件发生时处理， 无事件时跳过
                    //附件buffer 伴随该selectionkey & socketchannel
                    //关注 read 事件
                    sc.register(selector, SelectionKey.OP_READ, buffer);
                } else if (key.isReadable()) {
                    //将服务器断开的情况catch
                    try {
                        SocketChannel channel = (SocketChannel) key.channel();
                        ssc.configureBlocking(false);
                        //此处需要关注消息边界问题
                        ByteBuffer buffer = (ByteBuffer) key.attachment();
                        int read = channel.read(buffer);
                        // 若正常断开，此时
                        if (read == -1) {
                            key.cancel();
                        } else {
                            buffer.flip();
                            while (buffer.hasRemaining()) {
                                System.out.print((char) buffer.get());
                            }
                        }
                        //不然selector会以为buffer没有读完
                        buffer.clear();
                    } catch (IOException e) {
                        //若在读取时遇到报错，则删除取消该channel
                        key.cancel();
                    }
                }
                //处理完一个sectionkey 必须要删掉，防止selector 中的selectedKeys 再次获取到
                iterator.remove();
            }

        }
    }

    private static void writerServer() throws IOException {
        //创建 serversocketchannel
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(8080));
        ssc.configureBlocking(false);
        //ssc 注册进一个selector
        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                if (key.isAcceptable()) {
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                    sc.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(4));
                    //返回给client
                    ByteBuffer resp = StandardCharsets.UTF_8.encode("hello");
                    //当 resp的内容很长时，可能需要多次发送
                    while(resp.hasRemaining()){
                        sc.write(resp);
                    }
                }
            }
        }
    }

    private static void split(ByteBuffer source) {
        ByteBuffer target = null;
        source.flip();
        for (int i = 0; i < source.limit(); i++) {
            if (source.get(i) == '\n') {
                int length = i + 1 - source.position();
                target = ByteBuffer.allocate(length);
                for (int j = 0; j < length; j++) {
                    target.put(source.get());
                }
                target.flip();
                while (target.hasRemaining()) {
                    System.out.print((char) target.get());
                }
            }
        }
        //精妙之处在于 get(i) 不会移动position, get()会
        source.compact();
    }

    private static void receiverClient() throws IOException {
        try (SocketChannel sc = SocketChannel.open()) {
            sc.connect(new InetSocketAddress("localhost", 8080));
            System.out.println("waiting");
        }
    }

    private static void writerClient() {
        try (SocketChannel sc = SocketChannel.open()) {
            sc.connect(new InetSocketAddress("localhost", 8080));
            //receive data
            int count = 0;
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            while (true) {
                int read = sc.read(buffer);
                if (read == -1) {
                    break;
                }
                count += read;
                buffer.clear();
            }
            System.out.println(count);
        } catch (IOException e) {
        }
    }

    private static void commonApi() throws IOException {
        Selector selector = Selector.open();

        //阻塞直到有事件发生 （最常用）
        int count1 = selector.select();

        //阻塞 直到有事件发生 ｜ 超时   （netty中常用)
        long timeout = 100L;
        int count2 = selector.select(timeout);

        //不阻塞，立刻返回
        int count3 = selector.selectNow();

    }

}
